// $Id: KeyedCollection.cs 10 2008-02-29 15:58:02Z mcartoixa $
/*
 *  Copyright (C) 2007 Noury Solutions
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 */
/*! \file
 * \brief Definition of Domain Model keyed collections.
 *
 * \author $Author: mcartoixa $
 * \date $Date: 2008-02-29 16:58:02 +0100 (ven., 29 févr. 2008) $
 * \version $Revision: 10 $
 */

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;

namespace Salamanca.DataAccess.Collections
{


    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Arguments of a <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> collection saving event.</summary>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public sealed class DomainModelCollectionSavingEventArgs<TDomainModel>:
        PersistenceEventArgs
        where TDomainModel:
            IDomainModel
    {

        /// <summary>Constructor.</summary>
        public DomainModelCollectionSavingEventArgs(IList<TDomainModel> addedItems, IList<TDomainModel> removedItems, IPersistenceContext persistenceContext):
            base(persistenceContext)
        {
            _AddedItems=addedItems;
            _RemovedItems=removedItems;
        }

        /// <summary>Items added to the collection since its last save.</summary>
        public IList<TDomainModel> AddedItems
        {
            get
            {
                return _AddedItems;
            }
        }

        /// <summary>Items removed from the collection since its last save.</summary>
        public IList<TDomainModel> RemovedItems
        {
            get
            {
                return _RemovedItems;
            }
        }

        /// <summary>Items added to the collection since its last save.</summary>
        private IList<TDomainModel> _AddedItems;
        /// <summary>Items removed from the collection since its last save.</summary>
        private IList<TDomainModel> _RemovedItems;

    }



    ///////////////////////////////////////////////////////////////////////////
    ///
    /// <summary>Implements a  <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> keyed collection.</summary>
    /// <remarks>
    /// <para>This collection is designed to handle new <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instances and their temporary keys.
    /// An event handler is automatically registered to the <see cref="IPersistent.Saved" /> event, that triggers appropriate action when a definitive primary key
    /// is assigned to the <include file="../doc/Global.doc.xml" path="doc/template[@name='DomainModel']/*" /> instance.</para>
    /// </remarks>
    ///
    ///////////////////////////////////////////////////////////////////////////

    public class DomainModelKeyedCollection<TDomainModel>:
        KeyedCollection<IPrimaryKey, TDomainModel>,
        IPersistent
        where TDomainModel:
            IDomainModel
    {

        /// <summary>Constructor.</summary>
        public DomainModelKeyedCollection():
            base()
        {
        }

        /// <summary>Constructor.</summary>
        public DomainModelKeyedCollection(IEnumerable<TDomainModel> collection):
            this()
        {
            foreach (TDomainModel dm in collection)
                Add(dm);

            Reset();
        }

        /// <summary>Saves all the elements contained in the collection.</summary>
        public void Save(IPersistenceContext context)
        {
            Debug.Assert(context!=null);
            if (context==null)
                throw new ArgumentNullException("context");

            if (_SaveLock==null || !_SaveLock.Locked)
                using (_SaveLock=new PersistenceLock())
                {
                    OnSaving(
                        new DomainModelCollectionSavingEventArgs<TDomainModel>(
                            new ReadOnlyCollection<TDomainModel>(_AddedItems.ToArray()), // ToArray() is the short way to make a copy
                            new ReadOnlyCollection<TDomainModel>(_RemovedItems.ToArray()), // ToArray() is the short way to make a copy
                            context
                        )
                    );

                    // The keyed collection will be modified, so we have to make a copy first to prevent an exception in the following foreach statement
                    List<TDomainModel> tbs=new List<TDomainModel>(Items);
                    foreach (TDomainModel dm in tbs)
                    {
                        IPersistent p=dm as IPersistent;
                        if (p!=null)
                            p.Save(context);
                    }

                    Reset();

                    OnSaved(new PersistenceEventArgs(context));
                }
        }

        /// <summary>Deletes all the elements contained in the collection.</summary>
        public void Delete(IPersistenceContext context)
        {
            Debug.Assert(context!=null);
            if (context==null)
                throw new ArgumentNullException("context");

            if (_DeleteLock==null || !_DeleteLock.Locked)
                using (_DeleteLock=new PersistenceLock())
                {
                    OnDeleting(new PersistenceEventArgs(context));

                    // The keyed collection will be modified, so we have to make a copy first to prevent an exception in the following foreach statement
                    List<TDomainModel> tbd=new List<TDomainModel>(Items);
                    foreach (TDomainModel dm in tbd)
                    {
                        IPersistent p=dm as IPersistent;
                        if (p!=null)
                            p.Delete(context);
                    }

                    Reset();

                    OnDeleted(new PersistenceEventArgs(context));
                }
        }

        /// <summary>Removes all elements from the KeyedCollection.</summary>
        protected override void ClearItems()
        {
            _RemovedItems.AddRange(Items);
            foreach (TDomainModel dm in _AddedItems)
                _RemovedItems.Remove(dm);
            _AddedItems.Clear();

            base.ClearItems();
        }

        /// <summary>When implemented in a derived class, extracts the key from the specified element.</summary>
        protected override IPrimaryKey GetKeyForItem(TDomainModel item)
        {
            return item.Id;
        }

        /// <summary>Inserts an element into the <see cref="DomainModelKeyedCollection{TDomainModel}" /> at the specified index.</summary>
        protected override void InsertItem(int index, TDomainModel item)
        {
            RegisterEvents(item);

            base.InsertItem(index, item);
        }

        /// <summary>Removes the element at the specified index of the <see cref="DomainModelKeyedCollection{TDomainModel}" />.</summary>
        protected override void RemoveItem(int index)
        {
            UnregisterEvents(this[index]);

            base.RemoveItem(index);
        }

        /// <summary>Replaces the item at the specified index with the specified item.</summary>
        protected override void SetItem(int index, TDomainModel item)
        {
            UnregisterEvents(this[index]);
            RegisterEvents(item);

            base.SetItem(index, item);
        }

        /// <summary>Raises the <see cref="Deleting" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnDeleting(PersistenceEventArgs e)
        {
            if (Deleting!=null)
                Deleting(this, e);
        }

        /// <summary>Raises the <see cref="Deleted" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnDeleted(PersistenceEventArgs e)
        {
            if (Deleted!=null)
                Deleted(this, e);
        }

        /// <summary>Raises the <see cref="Saving" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnSaving(DomainModelCollectionSavingEventArgs<TDomainModel> e)
        {
            if (_Saving!=null)
                _Saving(this, e);
        }

        /// <summary>Raises the <see cref="Saved" /> event.</summary>
        /// <remarks>
        ///   <include file="../doc/Global.doc.xml" path="doc/template[@name='OnEvent']/*" />
        /// </remarks>
        protected virtual void OnSaved(PersistenceEventArgs e)
        {
            if (Saved!=null)
                Saved(this, e);
        }

        /// <summary>Registers new events to the specified item.</summary>
        private void RegisterEvents(TDomainModel item)
        {
            if (!_RemovedItems.Remove(item))
                _AddedItems.Add(item);

            item.Deleted+=ItemDeleted;
            if (item.IsNew)
            {
                item.Saving+=ItemSaving;
                item.Saved+=ItemSaved;
            }
        }

        /// <summary>Unregisters the events from the specified item.</summary>
        private void UnregisterEvents(TDomainModel item)
        {
            if (!_AddedItems.Remove(item))
                _RemovedItems.Add(item);

            item.Deleted-=ItemDeleted;
            item.Saving-=ItemSaving;
            item.Saved-=ItemSaved;
        }

        /// <summary>Action performed when an item of the collection is deleted.</summary>
        private void ItemDeleted(object sender, PersistenceEventArgs e)
        {
            IDomainModel dm=(IDomainModel)sender;
            if (dm!=null)
                dm.Deleted-=ItemDeleted;

            Remove((TDomainModel)sender);
        }

        /// <summary>Action performed when an item of the collection is about to be saved.</summary>
        private void ItemSaving(object sender, PersistenceEventArgs e)
        {
            IDomainModel dm=(IDomainModel)sender;
            if (dm!=null)
            {
                dm.Saving-=ItemSaving;

                Dictionary.Remove(dm.Id); // Remove the reference with the temporary key
            }
        }

        /// <summary>Action performed when an item of the collection is saved.</summary>
        private void ItemSaved(object sender, PersistenceEventArgs e)
        {
            IDomainModel dm=(IDomainModel)sender;
            if (dm!=null)
            {
                dm.Saved-=ItemSaved;

                Dictionary.Add(dm.Id, (TDomainModel)dm); // Add the final reference
            }
        }

        /// <summary>Resets the internal state of the collection.</summary>
        internal void Reset()
        {
            _AddedItems.Clear();
            _RemovedItems.Clear();
        }

        private List<TDomainModel> _AddedItems=new List<TDomainModel>();
        private List<TDomainModel> _RemovedItems=new List<TDomainModel>();
        /// <summary>Persistence lock used to avoid reentrance.</summary>
        private PersistenceLock _DeleteLock;
        /// <summary>Persistence lock used to avoid reentrance.</summary>
        private PersistenceLock _SaveLock;

        /// <summary>Occurs when the instance is about to be deleted (<see cref="Delete(IPersistenceContext)" />).</summary>
        public event EventHandler<PersistenceEventArgs> Deleting;
        /// <summary>Occurs when the instance has been deleted (<see cref="Delete(IPersistenceContext)" />).</summary>
        public event EventHandler<PersistenceEventArgs> Deleted;
        /// <summary>Occurs when the instance is about to be saved (<see cref="Save(IPersistenceContext)" />).</summary>
        public event EventHandler<DomainModelCollectionSavingEventArgs<TDomainModel>> Saving
        {
            add
            {
                _Saving+=new EventHandler<PersistenceEventArgs>(
                    delegate(object sender, PersistenceEventArgs e) {
                        value(sender, (DomainModelCollectionSavingEventArgs<TDomainModel>)e);
                    }
                );
            }
            remove
            {
                _Saving-=new EventHandler<PersistenceEventArgs>(
                    delegate(object sender, PersistenceEventArgs e) {
                        value(sender, (DomainModelCollectionSavingEventArgs<TDomainModel>)e);
                    }
                );
            }
        }

        event EventHandler<PersistenceEventArgs> IPersistent.Saving
        {
            add
            {
                _Saving+=value;
            }
            remove
            {
                _Saving-=value;
            }
        }
        /// <summary>Occurs when the instance has been saved (<see cref="Save(IPersistenceContext)" />).</summary>
        public event EventHandler<PersistenceEventArgs> Saved;

        private event EventHandler<PersistenceEventArgs> _Saving;


    }
}
